//////////////////////////////////////////////////////////////////////////

#ifndef _SERVERCONNECTIONIMPL_H_
#include "serverconnectionimpl.h"
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef _INNERPACKET_H_
#include "innerpacket.h"
#endif

#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>

//////////////////////////////////////////////////////////////////////////


// UDP socket ptr
udp::socket* ServerConnectionImpl::m_UDPSocket = NULL;

// UDP endpoint
udp::endpoint ServerConnectionImpl::m_UDPRecvEndpoint;

// Flags
bool ServerConnectionImpl::m_UDPAcceptStarted = false;

// TCP acceptor ptr
tcp::acceptor* ServerConnectionImpl::m_Acceptor = NULL;

// Connections by endpoints
ServerConnectionImpl::ConnectionsByEndpoints
ServerConnectionImpl::m_ConnectionsByEndpoints;

// Endpoints by connections
ServerConnectionImpl::EndpointsByConnections
ServerConnectionImpl::m_EndpointsByConnections;

// Connections by address
ServerConnectionImpl::ConnectionsByAddress
ServerConnectionImpl::m_ConnectionsByAddress;



//////////////////////////////////////////////////////////////////////////


ServerConnectionImpl::ServerConnectionImpl()
: m_TCPSocket( BoostService::GetIOService() ),
  m_UDPEstablished(false)
{
}


//////////////////////////////////////////////////////////////////////////


ServerConnectionImpl::~ServerConnectionImpl()
{
    // Clear tables
    m_ConnectionsByEndpoints.erase( m_EndpointsByConnections[this] );
    m_EndpointsByConnections.erase(this);
    m_ConnectionsByAddress.erase(m_TCPEndpointString);
}


//////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::SendTCPInner( const NetworkPacket& _data )
{
    // Send data through TCP
    m_TCPSocket.async_send( boost::asio::buffer( _data.Data(), _data.Size() ),
        boost::bind( &ServerConnectionImpl::HandleSendTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );
}


//////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::SendUDPInner( const NetworkPacket& _data )
{
    // Send data through UDP
    GetUDPSocket()->send_to( boost::asio::buffer( _data.Data(), _data.Size() ), GetUDPEndpoint(this) );
    ConnectionImpl::HandleSendUDP();
}


//////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::SendTCP( const NetworkPacket& _data )
{
    // Create inner packet from user packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // Send inner packet
    SendTCPInner(innerPacket);
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::SendUDP( const NetworkPacket& _data )
{
    // Create inner packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // And send it
    SendUDPInner(innerPacket);
}



////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::StartReceiveTCP()
{
    // Recv data through TCP
    m_TCPSocket.async_receive( boost::asio::buffer(m_RecvBufferTCP, MAX_BUFFER_SIZE),
        boost::bind( &ServerConnectionImpl::HandleRecvTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::StartReceiveUDP()
{
    // Recv data through UDP
    GetUDPSocket()->async_receive_from( boost::asio::buffer(m_RecvBufferUDP, MAX_BUFFER_SIZE),
        m_UDPRecvEndpoint,
        boost::bind( &ServerConnectionImpl::HandleRecvUDP,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleSendTCP( const error_code& _error,
                                        size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        std::cout << "ERROR SEND TCP: " << _error.message() << std::endl;
        return;
    }

    ConnectionImpl::HandleSendTCP();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleRecvTCP( const error_code& _error, size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        ConnectionImpl::HandleDisconnect();
        return;
    }


    // Compose inner packet from data buffer
    InnerPacket innerPacket(m_RecvBufferTCP, _bytesTransferred);

    // Get its code
    switch ( innerPacket.UnPackCode() )
    {
    // Data packet received
    case InnerPacket::REGULAR:
        HandleRecvTCPRegular(innerPacket);
        break;

    default:
        assert(!"Invalid packet type");
        break;
    }

    // Start receive TCP again
    StartReceiveTCP();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleRecvTCPRegular( const InnerPacket& _innerPacket )
{
    m_RecvPacketTCP = _innerPacket;
    ConnectionImpl::HandleRecvTCP();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleRecvUDP( const error_code& _error, size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        // Some strange bugs...
        if (_error.value() == 10022 || _error.value() == 10061)
            StartReceiveUDP();
        else
        {
            assert(!"Unknown UDP receive error");
            std::cout << "ERROR RECV UDP: " << _error.message() << std::endl;
        }

        return;
    }


    // Compose inner packet from data buffer
    InnerPacket innerPacket(m_RecvBufferUDP, _bytesTransferred);

    // Get its code
    switch ( innerPacket.UnPackCode() )
    {
    // Regular packet with client data
    case InnerPacket::REGULAR:
        HandleRecvUDPRegular(innerPacket);
        break;

    // Accept connection confirmation, bind UDP endpoint with connection
    case InnerPacket::CONNECT:
        HandleRecvUDPConnect(innerPacket);
        break;

    default:
        assert(!"Invalid packet type");
        break;
    }

    // Start receive UDP again
    StartReceiveUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleRecvUDPRegular( const InnerPacket& _innerPacket )
{
    // Handle receive packet on specified connection
    ConnectionsByEndpoints::iterator it = m_ConnectionsByEndpoints.find(m_UDPRecvEndpoint);
    if ( it != m_ConnectionsByEndpoints.end() )
    {
        ServerConnectionImpl* conn = it->second;
        if (conn)
        {
            m_RecvPacketUDP = _innerPacket;
            conn->ConnectionImpl::HandleRecvUDP();
        }
    }
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleRecvUDPConnect( const InnerPacket& _innerPacket )
{
    std::string TCPEndpoint( _innerPacket.Data(), _innerPacket.Size() );
    ConnectionsByAddress::iterator it = m_ConnectionsByAddress.find(TCPEndpoint);
    if ( it != m_ConnectionsByAddress.end() )
    {
        ServerConnectionImpl* currConnection = (*it).second;

        m_ConnectionsByEndpoints[m_UDPRecvEndpoint] = currConnection;
        m_EndpointsByConnections[currConnection] = m_UDPRecvEndpoint;

        // Convert udp endpoint to string and print it
        std::string udpEndpoint =
            boost::lexical_cast<std::string>( m_UDPRecvEndpoint );
        std::cout << "remote udp endpoint: " << udpEndpoint << std::endl;

        // Send confirmation to the client
        InnerPacket packet( "Confirm", sizeof("Confirm"), InnerPacket::CONFIRM );
        packet.PackCode();

        currConnection->SendTCPInner(packet);
        currConnection->SetUDPEstablished();
    }
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::Accept()
{
    // Accept new TCP connections
    GetTCPAcceptor()->async_accept( m_TCPSocket,
        boost::bind( &ServerConnectionImpl::HandleAccept,
        this,
        boost::asio::placeholders::error ) );
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::Connect( const char* _address )
{
    throw "Should never reach here!";
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::Close()
{
    m_TCPSocket.close();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::HandleAccept( const error_code& _error )
{
    if (_error)
    {
        std::cout << "ERROR ACCEPT: " << _error.message() << std::endl;
        return;
    }


    // Setup UDP
    StartUDPAccept();

    // Start receive UDP
    StartReceiveUDP();

    // Start receiving TCP packets
    StartReceiveTCP();

    // Send TCP endpoint to the client, client must confirm this endpoint by UDP
    SendTCPEndpointToClient();

    std::cout << "Preparing to accept new connection" << std::endl;

    ConnectionImpl::HandleAccept();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::StartUDPAccept()
{
    if (!m_UDPAcceptStarted)
    {
        GetUDPSocket()->open( udp::v4() );
        GetUDPSocket()->bind( udp::endpoint(udp::v4(), m_Port) );
        m_UDPAcceptStarted = true;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::SendTCPEndpointToClient()
{
    // Convert tcp endpoint to string and save it
    m_TCPEndpointString =
        boost::lexical_cast<std::string>( m_TCPSocket.remote_endpoint() );

    // Print it
    std::cout << "remote tcp endpoint: " << m_TCPEndpointString << std::endl;

    // Add record to table
    m_ConnectionsByAddress[m_TCPEndpointString] = this;

    // Compose inner packet
    InnerPacket innerPacket(m_TCPEndpointString.c_str(),
                            m_TCPEndpointString.size(),
                            InnerPacket::CONNECT);
    innerPacket.PackCode();

    // Send it
    SendTCPInner(innerPacket);
}


////////////////////////////////////////////////////////////////////////////////


tcp::acceptor* ServerConnectionImpl::GetTCPAcceptor()
{
    if (!m_Acceptor)
        m_Acceptor = new tcp::acceptor( BoostService::GetIOService(), tcp::endpoint(tcp::v4(), m_Port) );

    return m_Acceptor;
}


////////////////////////////////////////////////////////////////////////////////


udp::socket* ServerConnectionImpl::GetUDPSocket()
{
    if (!m_UDPSocket)
        m_UDPSocket = new udp::socket( BoostService::GetIOService() );

    return m_UDPSocket;
}


////////////////////////////////////////////////////////////////////////////////


udp::endpoint ServerConnectionImpl::GetUDPEndpoint( ServerConnectionImpl* _this )
{
    EndpointsByConnections::iterator it = m_EndpointsByConnections.find(_this);
    if ( it != m_EndpointsByConnections.end() )
        return it->second;
    else
        return udp::endpoint();
}


////////////////////////////////////////////////////////////////////////////////


bool ServerConnectionImpl::IsConnected() const
{
    return m_UDPEstablished && m_TCPSocket.is_open();
}


////////////////////////////////////////////////////////////////////////////////


void ServerConnectionImpl::SetUDPEstablished()
{
    m_UDPEstablished = true;
}


////////////////////////////////////////////////////////////////////////////////
